package group.cs.purdue.edu;

import global.sgb.cs.purdue.edu.AttrType;
import global.sgb.cs.purdue.edu.Distance;
import global.sgb.cs.purdue.edu.L2distance;
import global.sgb.cs.purdue.edu.LinfinityDistance;
import global.sgb.cs.purdue.edu.Tuple;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

public class Group {

	private HashMap<Integer, Tuple> g;
	private int groupid;
	
	/**the center point of one group*/
	Tuple center;
	
	/**the filed that is used to judge distance*/
	private int []FildeList;
	
	/**distance method to compute the tuple difference*/
	private int distanceMetric;
	
	/**parameter for the distance*/
	private double EPS;
	
	/**update center label, if true, once add new tuples, we need to update the center*/
	boolean update_center=true;
	
	/**update the group rectanlge bounds when adding new points*/
	boolean update_bound=true;
	
	public Group(int id) {
		// TODO Auto-generated constructor stub
		this.g=new HashMap<Integer, Tuple>();
		this.groupid=id;
	}
	
	/**
	 * construction function for the group
	 * @param id
	 * @param update_center
	 * @param update_bound
	 * @param distanceMetric
	 * @param filedlist
	 */
	public Group(int id, 
			boolean update_center, 
			boolean update_bound,
		    int distanceMetric,
			int []filedlist,
			double eps
			)
	{
		this.g=new HashMap<Integer, Tuple>();
		this.groupid=id;
		this.setDistanceMetric(distanceMetric);
		this.setUpdateCenter(update_center);
		this.setUpdate_bound(update_bound);
		this.setFildeList(filedlist);
		this.setEPS(eps);
	}

	/***************************************************/
	public double getEPS() {
		return EPS;
	}


	public void setEPS(double ePS) {
		EPS = ePS;
	}

	
	public boolean isUpdate_bound() {
		return update_bound;
	}

	public void setUpdate_bound(boolean update_bound) {
		this.update_bound = update_bound;
	}

	/**
	 * update the rectangle bounds when new points were added 
	 */
	public void updateRBound(Tuple T)
	{
		
	}
	
	/***************************************************/
	/**
	 * update the group center point
	 * @return
	 */
	public boolean isUpdateCenter() {
		return update_center;
	}

	public void setUpdateCenter(boolean update) {
		this.update_center = update;
	}
	
	public Tuple getCenter() {
		return center;
	}

	public void setCenter(Tuple center) {
		this.center = center;
	}
	
	/**
	 * @update the center point of bounds
	 */
	public void updateCenter(Tuple T)
	{
		if(this.getCenter()==null)
		{
			this.center=new Tuple(T.getSchema());
			
			for(int i:this.getFildeList())
			{
				float f1=Float.valueOf(T.getFiled(i).toString());
				this.center.setFiled(i, f1);
			}
			return;
		}
		
		for(int i:this.getFildeList())
		{
			float f1=Float.valueOf(T.getFiled(i).toString());
			float f2=Float.valueOf(this.center.getFiled(i).toString());
			
			/**get the sum of previous tuples in one dimension*/
			f2=f2*this.g.size();
			
			/**update the sum*/
			f2=f1+f2;
			
			/**get average*/
			
			f2=f2/(this.g.size()+1);
			
			/**update the group center*/
			this.center.setFiled(i, f2);
			
		}
	}
	/***************************************************/
	/**
	 * update the group center or the rectanlge bound
	 */
	public void updateGroup(Tuple T)
	{
		if(this.isUpdateCenter())
		{
			this.updateCenter(T);
			
		}else if(this.isUpdateCenter())
		{
			this.updateRBound(T);
		}
	}
	
	/***************************************************/
	public int[] getFildeList() {
		return FildeList;
	}

	public void setFildeList(int[] fildeList) {
		FildeList = fildeList;
	}

	public int getDistanceMetric() {
		return distanceMetric;
	}

	public void setDistanceMetric(int distanceMetric) {
		this.distanceMetric = distanceMetric;
	}


	/***************************************************/

	public void delete(Tuple t)
	{
		this.g.remove(t);
	}
	
	/**
	 * add one tuple into a group
	 * @param id
	 * @param t
	 */
	public void add(int id, Tuple t)
	{
		
		if(!g.containsKey(id))
		{
			/**update the group when add new points*/
			this.updateGroup(t);
			
			g.put(id, t);
		
		}
	}
	/**
	 * add a tuple into the group
	 * @param t
	 */
	public void add(Tuple t)
	{
		
		int id=(Integer) t.getFiled(0);
		
		if(!g.containsKey(id))
		{
			/**update the group when add new points*/
			this.updateGroup(t);
			
			g.put(id, t);
		
		}
	}
	
	
	
	/*****************************************************/
	/**
	 * @param t
	 * @return
	 * if the tuple can join one group, return true, or else return false
	 *  
	 */
	public boolean judgeJoinGroup(
			Tuple t, 
			int[] fileds, 
			TreeMap<Integer, Tuple> insidebound)
	{
		boolean inside=false;
		
		//method 1: brute force checking every points in the group to meet the requirement
		inside= bfJudge(t,fileds,insidebound);
		
		//method 2: check the rectangle bounds 
		//inside= checkboundJudge(t,fileds);
		
		//method 3: check the convext hull bound. 
		//inside=convexthullJudge(t,fileds);
		
		return inside;
		
	}

	/**
	 * 
	 * @param t
	 * @param fileds
	 * @return
	 * brute force checking every points in the group to meet the requirement
	 */
	private boolean convexthullJudge(Tuple t, int[] fileds) {
		// TODO Auto-generated method stub
		
		return false;
	}

	/**
	 * 
	 * @param t
	 * @param fileds
	 * @return
	 * method 2: check the rectangle bounds 
	 * and check the four points 
	 */
	private boolean checkboundJudge(Tuple t, int[] fileds) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * 
	 * @param t
	 * @param fileds
	 * @return
	 * method 3
	 * brute force checking methods 
	 */
	private boolean bfJudge(Tuple t, int[] fileds, TreeMap<Integer, Tuple> insidebound) {
		// TODO Auto-generated method stub
		
		int metric=this.getDistanceMetric();
		
		Distance d=null;
		
		if(metric==AttrType.L2)
		{
			d=new L2distance();
		}else
		{
			d=new LinfinityDistance();
		}
		
		Iterator<Entry<Integer, Tuple>> entries = this.g.entrySet().iterator();
		
		
		boolean door=true;
		
		while (entries.hasNext()) {
		    Map.Entry<Integer, Tuple> entry = entries.next();
		    //System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
		    
		    Tuple t2=entry.getValue();
		    
		    double distance= d.getDistance(this.getDistanceMetric(), t, t2, fileds);
		    
		    if(distance<=this.getEPS())
		    {
		    	//door=true;
		    	//insidebound.add(t2);
		    	insidebound.put((Integer)t2.getFiled(0), t2);
		    	
		    }else
		    {
		    	door=false;
		    }
		}
		
		return door;
	}
	
	
	/*****************************************************/
	
	public String toString()
	{
		StringBuffer s= new  StringBuffer();
		
		s.append("GID:"+this.groupid+"\n");
		
		Iterator<Entry<Integer, Tuple>> entries = this.g.entrySet().iterator();
		while (entries.hasNext()) {
			  Map.Entry<Integer, Tuple> entry = entries.next();
			 Tuple t2=entry.getValue();
			 s.append(t2.toString()+"\t");
		}
		return s.toString();
		
	}
	
	public String toTupleString()
	{
		StringBuffer s= new  StringBuffer();
		
		Iterator<Entry<Integer, Tuple>> entries = this.g.entrySet().iterator();
		while (entries.hasNext()) {
			  Map.Entry<Integer, Tuple> entry = entries.next();
			 Tuple t2=entry.getValue();
			 s.append(String.valueOf(t2.getFiled(0))+",");
			 //s.append(t2.toString()+",");
		}
		return s.toString();
		
	}


}
